/*
 * Copyright(c) 2018-? the original author[Wien(luweien@aliyun.com)] or authors.
 *
 */
package org.doll.it.sequence.impl;

import org.doll.it.sequence.enums.BusinessTypeEnum;
import org.doll.it.sequence.vo.SnowFlakeVO;

import lombok.extern.slf4j.Slf4j;

/**
 * 基于Twitter的Snowflake算法实现分布式高效有序ID生成器
 * [有序][不重复][整数][自增][不连续]
 * 
 * <br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * <br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 000 - 000000000 - 0000000000 <br>
 * <br>
 * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0<br>
 * <br>
 * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)
 * 得到的值），这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdWorker类的startTime属性）。41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * <br>
 * 3位的实例Id，可以部署在8个实例(0-7)<br>
 * <br>
 * 9位的业务Id，可以支持512种业务(0-511)<br>
 * <br>
 * 10位序列，毫秒内的计数，10位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生1024个ID序号(0-1023)<br>
 * <br>
 * <br>
 * 加起来刚好64位，为一个Long型。<br>
 * SnowFlake的优点是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞(由实例Id作区分)，并且效率较高
 * 
 */
@Slf4j
public class TwitterSnowFlake {

	/** 开始时间截 */
	private static final long TWEPOCH = 1520344019410L;
	/** 实例Id所占的位数 */
	private static final long INSTANCE_ID_BITS = 3L;
	/** 业务编码所占的位数 */
	private static final long BUSINESS_ID_BITS = 9L;
	/** 序列所占的位数 */
	private static final long SEQUENCE_BITS = 10L;

	/** 支持的最大workId */
	private static final long MAX_INSTANCE_ID = -1L ^ (-1L << INSTANCE_ID_BITS);
	/** 支持的最大业务编码 */
	private static final long MAX_BUSINESS_ID = -1L ^ (-1L << BUSINESS_ID_BITS);
	/** 支持的最大序列 */
	private static final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BITS);

	/** 时间截向左移位数 */
	private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + BUSINESS_ID_BITS + INSTANCE_ID_BITS;
	/** 实例Id向左移位数 */
	private static final long INSTANCE_ID_LEFT_SHIFT = SEQUENCE_BITS + BUSINESS_ID_BITS;
	/** 业务编码向左移位数 */
	private static final long BUSINESS_ID_LEFT_SHIFT = SEQUENCE_BITS;

	/** 上次生成ID的时间截 */
	private long lastTimestamp = -1L;
	/** 实例Id */
	private long instanceId;
	/** 毫秒内序列 */
	private long sequence = 0L;

	public TwitterSnowFlake(long instanceId) {
		if (instanceId > MAX_INSTANCE_ID || instanceId < 0) {
			throw new IllegalArgumentException(String.format(
					"Instance Id can't be greater than %d or less than 0. current is %d", MAX_INSTANCE_ID, instanceId));
		}
		this.instanceId = instanceId;
		log.info("SnowFlake: instanceId is [0-{}], businessId is [0-{}]. current instance id is {}", MAX_INSTANCE_ID,
				MAX_BUSINESS_ID, instanceId);
	}

	/**
	 * 获得一个ID (该方法是线程安全的)
	 * @return
	 */
	public synchronized long nextId(long businessId) {
		if (businessId > MAX_BUSINESS_ID || businessId < 0) {
			throw new IllegalArgumentException(
					String.format("BusinessId Id can't be greater than %d or less than 0. current is %d",
							MAX_BUSINESS_ID, businessId));
		}
		long timestamp = timeGen();

		// 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
		if (timestamp < lastTimestamp) {
			// 闰秒
			long offset = lastTimestamp - timestamp;
			if (offset <= 5) {
				try {
					wait(offset << 1);
					timestamp = timeGen();
					if (timestamp < lastTimestamp) {
						throw new RuntimeException(String
								.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			} else {
				throw new RuntimeException(
						String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
			}
		}

		if (lastTimestamp == timestamp) {
			sequence = (sequence + 1) & MAX_SEQUENCE;
			// 毫秒内序列溢出
			if (sequence == 0) {
				// 阻塞到下一个毫秒,获得新的时间戳
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			// 时间戳改变，毫秒内序列重置
			sequence = 0;
		}

		// 上次生成ID的时间截
		lastTimestamp = timestamp;

		// 移位并通过或运算拼到一起组成64位的ID
		return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | (instanceId << INSTANCE_ID_LEFT_SHIFT)
				| (businessId << BUSINESS_ID_LEFT_SHIFT) | sequence;
	}

	/**
	 * 阻塞到下一个毫秒，直到获得新的时间戳
	 * @param lastTimestamp 上次生成ID的时间截
	 * @return 当前时间戳
	 */
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	/**
	 * 返回以毫秒为单位的当前时间
	 * @return 当前时间(毫秒)
	 */
	private long timeGen() {
		return System.currentTimeMillis();
	}

	/**
	 * 根据Id，反解出各段信息
	 * @param id 正确的long型Id
	 * @return 各段信息的VO
	 */
	public static SnowFlakeVO parse(Long id) {
		verifyId(id);
		long timestamp = (id >>> TIMESTAMP_LEFT_SHIFT) + TWEPOCH;
		long instanceId = (id >>> INSTANCE_ID_LEFT_SHIFT) & MAX_INSTANCE_ID;
		long businessId = (id >>> BUSINESS_ID_LEFT_SHIFT) & MAX_BUSINESS_ID;
		long sequence = id & MAX_SEQUENCE;
		return SnowFlakeVO.builder().timestamp(timestamp).instanceId(instanceId).businessId(businessId)
				.sequence(sequence).id(id).businessType(BusinessTypeEnum.valueOf((int) businessId)).build();
	}

	/**
	 * 校验ID的业务类型
	 * @param id
	 * @param businessType
	 * @return
	 */
	public static boolean validBusinessType(Long id, BusinessTypeEnum businessType) {
		return businessType == null ? false : validBusinessType(id, businessType.getCode());
	}

	/**
	 * 校验ID的业务类型
	 * @param id
	 * @param businessType
	 * @return
	 */
	public static boolean validBusinessType(Long id, Integer businessId) {
		if (id != null && id > 0 && businessId != null) {
			long bId = (id >>> BUSINESS_ID_LEFT_SHIFT) & MAX_BUSINESS_ID;
			return businessId == bId;
		}
		return false;
	}

	/**
	 * 验证Id是否正确
	 * @param id SnowFlakeId
	 * @return true:验证通过; false:验证失败
	 */
	public static void verifyId(Long id) {
		if (id == null || id <= 0) {
			throw new RuntimeException("Id must be greater than 0. current is " + id);
		}
	}

}